package com.atguigu.tingshu.account.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    @Autowired
    private UserAccountMapper userAccountMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<AccountLockResultVo> checkAndLockAccount(AccountLockVo accountLockVo) {
        // OpenFeign重试机制/超时机制
        // OpenFeign重试（默认不重试：自带了重试机制【重试5次】 自定义重试逻辑）
        // 假设：开启了openFeign的重试机制（幂等性保证：openfeign保证幂等性）
        // 10000s
        // 1.获取属性值
        Long userId = accountLockVo.getUserId();
        BigDecimal amount = accountLockVo.getAmount();
        String content = accountLockVo.getContent();
        String orderNo = accountLockVo.getOrderNo();
        String userAccountResultKey = "user:account:data" + orderNo;

        // 2.检查账户余额是否充足
        // 如果充足：锁定，返回锁定成功的对象
        // 如果不足，锁定失败，返回失败的状态和提示消息
        // CAS(比较并交换)利用CAS思想在MYSQL层面写锁实现互斥机制
        // 3.检查和锁定余额(setIfAbsent Lock)
        //3.1解决幂等性操作(避免openfeign重试机制重复发送请求)
        List<String> range = redisTemplate.opsForList().range("feign:retry:msg:container" + orderNo, -1, 0);
        if (range.contains(orderNo)) {
            return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_REPEAT);
        }
        try {
            Long aLong = redisTemplate.opsForList().leftPush("feign:retry:msg:container" + orderNo, orderNo);
            if (aLong == 1) {
                int a=userAccountMapper.checkAndLockAccount(userId, amount);
                if (a==0){
                    throw new GuiguException(201,"账户余额不足");
                }
                // 4.准备AccountLockResultVo对象
                AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
                accountLockResultVo.setUserId(userId);
                accountLockResultVo.setAmount(amount);
                accountLockResultVo.setContent(content);
                //5.存入redis(便于解锁和扣减余额方便)
                redisTemplate.opsForValue().set(userAccountResultKey, JSON.toJSONString(accountLockResultVo));
                // 6.保存账户的流水
                this.log(userId, "锁定" + content, "1202", amount, orderNo);
                //7.给上游
                return Result.ok(accountLockResultVo);

            }else {
                return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_REPEAT);
            }
        } catch (Exception e) {
            // 我正常干完不能删（别人进来不能干） 只有我干的期间出现了异常 才允许让别人进来继续干
            redisTemplate.delete("feign:retry:msg:container" + orderNo);
            throw new GuiguException(201, "ResultCodeEnum.ACCOUNT_LOCK_ERROR");
        }
    }

    //获取账户可用余额
    @Override
    public BigDecimal getAvailableAmount() {
        Long userId = AuthContextHolder.getUserId();
        LambdaQueryWrapper<UserAccount> queryWrapper = new LambdaQueryWrapper<UserAccount>().eq(UserAccount::getUserId, userId);
        UserAccount userAccount = userAccountMapper.selectOne(queryWrapper);
        if (userAccount == null) {
            return new BigDecimal("0.00");
        }
        return userAccount.getAvailableAmount();
    }
    public void log(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);
        userAccountDetailMapper.insert(userAccountDetail);
    }
}
